﻿/*******************************************************************
This file is part of WP7DropBox.

WP7DropBox is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

WP7DropBox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with WP7DropBox.  If not, see <http://www.gnu.org/licenses/>.
*******************************************************************/
namespace l3v5y.Dropbox
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.IO.IsolatedStorage;
    using System.Net;

    public class WP7Dropbox
    {
        public delegate void token_complete(Token response);
        public delegate void account_info_complete(AccountInfoResponse response);
        public delegate void metadata_complete(FolderMetadata response);
        public delegate void delete_complete();
        public event token_complete TokenComplete;
        public event metadata_complete MetadataComplete;
        public event account_info_complete AccountInfoComplete;
        public event metadata_complete FileopsCopyComplete;
        public event metadata_complete FileopsCreateFolderComplete;
        public event delete_complete FileopsDeleteComplete;
        public event metadata_complete FileopsMoveComplete;
        private static string API_ROOT = "https://api.dropbox.com/0/";
        private static string API_CONTENT_ROOT = "https://api-content.dropbox.com/0/";
        private static string API_TOKEN = "token";
        private static string API_ACCOUNT = "account";
        private static string API_ACCOUNT_INFO = "account/info";
        private static string API_FILES = "files/dropbox";
        private static string API_METADATA = "metadata/dropbox";
        private static string API_THUMBNAILS = "thumbnails/dropbox";
        private static string API_FILEOPS_COPY = "fileops/copy";
        private static string API_FILEOPS_CREATE_FOLDER = "fileops/create_folder";
        private static string API_FILEOPS_DELETE = "fileops/delete";
        private static string API_FILEOPS_MOVE = "fileops/move";
        private OAuthHelper helper = new OAuthHelper();


        /// <summary>
        /// Token containing app key and secret strings
        /// </summary>
        private Token app = new Token();

        /// <summary>
        /// Token containing user key and secret strings
        /// </summary>
        private Token user = new Token();


        /// <summary>
        /// Default constructor
        /// </summary>
        /// <param name="key">Apps API key</param>
        /// <param name="secret">Apps API secret</param>
        public WP7Dropbox(string key, string secret)
        {
            TokenComplete += new token_complete(OAuthRequest_TokenComplete);
            app.token = key;
            app.secret = secret;
            helper = new OAuthHelper(app);
        }
        /// <summary>
        /// constructor overload to allow using preretrieved token
        /// </summary>
        /// <param name="key">Apps API key</param>
        /// <param name="secret">Apps API secret</param>
        /// <param name="response">User token</param>
        public WP7Dropbox(string key, string secret, Token response)
        {
            user = response;
            app.token = key;
            app.secret = secret;
            helper = new OAuthHelper(app, user);
        }

        /// <summary>
        /// requests a new token for user
        /// </summary>
        /// <param name="username">users email address</param>
        /// <param name="password">users password</param>
        public void token(string username, string password)
        {
            List<string> parms = new List<string>();
            parms.Add("email=" + username);
            parms.Add("password=" + password);
            string requesturl = helper.OAuthRequestUrl(API_ROOT + API_TOKEN, parms);


            Helper.HttpWebRequestGet((target) =>
            {
                HttpWebRequest wr = (HttpWebRequest)target.AsyncState;
                HttpWebResponse response = (HttpWebResponse)wr.EndGetResponse(target);
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    string sr = new StreamReader(response.GetResponseStream()).ReadToEnd();

                    Token token = Helper.Deserialize<Token>(sr);
                    try
                    {
                        TokenComplete.Invoke(token);
                    }
                    catch
                    {
                    }
                }
                else if ((int)response.StatusCode == (int)DropboxStatusCode.UserOverQuota)
                {
                    throw new DropboxError((DropboxStatusCode)(int)response.StatusCode);
                }


            }, requesturl);
        }
        /// <summary>
        /// Internally used to set user tokens after a token() call
        /// </summary>
        /// <param name="response">Token that uniquely identifies the user</param>
        private void OAuthRequest_TokenComplete(Token response)
        {
            user = response;
            helper = new OAuthHelper(app, user);
        }


        public void account(string email, string first_name, string last_name, string password)
        {
            List<string> parms = new List<string>();

            parms.Add("email=" + email);
            parms.Add("first_name=" + first_name);
            parms.Add("last_name=" + last_name);
            parms.Add("password=" + password);

            string requesturl = helper.ParmsRequestUrl(API_ROOT + API_ACCOUNT, parms);
             
            /*
             * email - Required. The email account of the user
first_name - Required. The user's first name
last_name - Required. The user's last name
password - Required. The password for the user
             * */
            throw new NotImplementedException();
        }

        public void accountinfo()
        {
            if (user == null)
            {
                throw new NullReferenceException("user is null");
            }
            string requesturl = helper.OAuthRequestUrl(API_ROOT + API_ACCOUNT_INFO, new List<string>());


            Helper.HttpWebRequestGet((target) =>
                {
                    HttpWebRequest wr = (HttpWebRequest)target.AsyncState;
                    HttpWebResponse response = (HttpWebResponse)wr.EndGetResponse(target);
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        string sr = new StreamReader(response.GetResponseStream()).ReadToEnd();

                        AccountInfoResponse air = Helper.Deserialize<AccountInfoResponse>(sr);
                        try
                        {
                            AccountInfoComplete.Invoke(air);
                        }
                        catch
                        {
                        }
                    }
                    else
                    {
                        throw new DropboxError((DropboxStatusCode)(int)response.StatusCode);
                    }


                }, requesturl);
        }

        /// <summary>
        /// Gets files from a DropBox location
        /// </summary>
        /// <param name="path">path of file to get</param>
        /// <param name="DownloadProgressChanged">event handler for progress changed</param>
        /// <param name="OpenReadCompleted">event handler for completion</param>
        public void filesget(string path, DownloadProgressChangedEventHandler DownloadProgressChanged,
            OpenReadCompletedEventHandler OpenReadCompleted)
        {
            string requesturl = helper.OAuthRequestUrl(API_ROOT + API_FILES + path.Replace(" ", "%20") + "/", new List<string>());
            
            WebClient webClient = new WebClient();
            webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgressChanged);
            webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(OpenReadCompleted);
            webClient.OpenReadAsync(new Uri(requesturl));
        }

        public void filespost(string path, string filename, UploadProgressChangedEventHandler UploadProgressChanged,
            OpenWriteCompletedEventHandler OpenWriteCompleted)
        {
            List<string> parms = new List<string>();

            parms.Add("file=" + filename);
            string requesturl = helper.OAuthRequestUrl(API_ROOT + API_FILES + path.Replace(" ", "%20") + "/", parms);

            WebClient webClient = new WebClient();

            webClient.UploadProgressChanged += new UploadProgressChangedEventHandler(UploadProgressChanged);
            webClient.OpenWriteCompleted += new OpenWriteCompletedEventHandler(OpenWriteCompleted);
            webClient.OpenWriteAsync(new Uri(requesturl));
            /*
            string requesturl = helper.OAuthRequestUrl(API_ROOT + API_FILES + path.Replace(" ", "%20") + "/", new List<string>(), "POST");
            Helper.HttpWebRequestPost((target) =>
            {
                HttpWebRequest request = (HttpWebRequest)target.AsyncState;
                try
                {
                    //FilesPostComplete(request.EndGetRequestStream(target));
                }
                catch
                {
                }
            }, requesturl);*/
        }

        /// <summary>
        /// The metadata API location provides the ability to retrieve file and folder metadata and manipulate the directory structure by moving or deleting files and folders.
        /// </summary>
        /// <param name="path">Directory of file/metadata</param>
        /// <param name="hash">Listing return values include a hash representing the state of the directory's contents. If you provide this argument to the metadata call, you give the service an opportunity to respond with a "304 Not Modified" status code instead of a full (potentially very large) directory listing. This argument is ignored if the specified path is associated with a file or if list=false</param>
        /// <param name="list">The strings true and false are valid values. true is the default. If true, this call returns a list of metadata representations for the contents of the directory. If false, this call returns the metadata for the directory itself.</param>
        /// <param name="file_limit">Default is 10000. When listing a directory, the service will not report listings containing more than file_limit files and will instead respond with a 406 (Not Acceptable) status response.</param>
        public void metadata(string path, string hash = null, bool list = true, int file_limit = 0)
        {

            List<string> parms = new List<string>();
            FolderMetadata fmi = null;

            if (IsolatedStorageSettings.ApplicationSettings.Contains(path))
            {
                fmi = (FolderMetadata)IsolatedStorageSettings.ApplicationSettings[path];

            }
            if (user.token == null)
            {
                return;
            }
            if (hash != null)
            {
                parms.Add("hash=" + hash);
            }
            else
            {
                if (fmi != null)
                {
                    parms.Add("hash=" + fmi.hash);
                }
            }
            if (!list)
            {
                parms.Add("list=false");
            }
            if (file_limit != 0)
            {
                parms.Add("file_limit=" + file_limit.ToString());
            }
            string requesturl = helper.OAuthRequestUrl(API_ROOT + API_METADATA + path.Replace(" ", "%20") + "/", parms);

            Helper.HttpWebRequestGet((target) =>
                {
                    HttpWebRequest wr = (HttpWebRequest)target.AsyncState;

                    HttpWebResponse response = (HttpWebResponse)wr.EndGetResponse(target);
                    FolderMetadata meta = new FolderMetadata();
                    if (response.StatusCode == HttpStatusCode.NotModified)
                    {
                        meta = fmi;
                    }
                    else if (response.StatusCode != HttpStatusCode.OK)
                    {
                        throw new DropboxError((DropboxStatusCode)(int)response.StatusCode);
                    }
                    else
                    {
                        string sr = new StreamReader(response.GetResponseStream()).ReadToEnd();
                        meta = Helper.Deserialize<FolderMetadata>(sr);
                    }
                    foreach (FileMetadata file in meta.Contents)
                    {
                        if (file.thumb_exists)
                        {
                            file.Thumbnail = thumbnails(file.path);
                        }
                    }
                    IsolatedStorageSettings.ApplicationSettings[path] = meta;
                    try
                    {
                        MetadataComplete.Invoke(meta);
                    }
                    catch
                    {
                    }
                }, requesturl);
        }
        /// <summary>
        /// Get a minimized thumbnail for a photo in three sizes: 'small' (32 x 32 px), 'medium' (64 x 64 px), and 'large' (128 x 128 px).
        /// </summary>
        /// <param name="path">Path of file to get thumbnail of</param>
        /// <param name="size">A string of ‘small', ‘medium', ‘large' which is optional (defaults to small).</param>
        /// <returns>url of thumbnail (one time request!)</returns>
        public string thumbnails(string path, string size = "small")
        {
            List<string> parms = new List<string>();
            if (size != null)
            {
                parms.Add("size=" + size);
            }
            return helper.OAuthRequestUrl(API_CONTENT_ROOT + API_THUMBNAILS + path.Replace(" ", "%20"), parms);
        }

        /// <summary>
        /// Converts a path to an OAuth request string
        /// </summary>
        /// <param name="path">path to file to get URL of</param>
        /// <returns></returns>
        public string filepath(string path)
        {
            return helper.OAuthRequestUrl(API_CONTENT_ROOT + API_FILES + path.Replace(" ", "%20"), new List<string>());
        }

        /// <summary>
        /// Copy a file or folder to a new location.
        /// </summary>
        /// <param name="from_path">specifies either a file or folder to be copied to the location specified by to_path. This path is interpreted relative to the location specified by root.</param>        
        /// <param name="to_path">specifies the destination path including the new name for file or folder. This path is interpreted relative to the location specified by root.</param>
        /// <param name="root">Specify the root relative to which from_path and to_path are specified. Valid values are dropbox and sandbox.</param>
        public void fileops_copy(string from_path, string to_path, string root = "dropbox")
        {

            List<string> parms = new List<string>();
            parms.Add("from_path=" + from_path);
            parms.Add("to_path=" + to_path);
            parms.Add("root=" + root);
            string requesturl = helper.OAuthRequestUrl(API_ROOT + API_FILEOPS_COPY, parms);


            Helper.HttpWebRequestGet((target) =>
            {
                HttpWebRequest wr = (HttpWebRequest)target.AsyncState;
                HttpWebResponse response = (HttpWebResponse)wr.EndGetResponse(target);
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    string sr = new StreamReader(response.GetResponseStream()).ReadToEnd();

                    FolderMetadata metadata = Helper.Deserialize<FolderMetadata>(sr);
                    try
                    {
                        FileopsCopyComplete.Invoke(metadata);
                    }
                    catch
                    {
                    }
                }
                else if ((int)response.StatusCode == (int)DropboxStatusCode.PathNotFound)
                {
                    throw new DropboxError((DropboxStatusCode)(int)response.StatusCode, "Failed to find path (" + from_path + ")");
                }
                else
                {
                    throw new DropboxError((DropboxStatusCode)(int)response.StatusCode);
                }
            }, requesturl);
        }

        /// <summary>
        /// Create a folder relative to the user's Dropbox root or the user's application sandbox folder.
        /// </summary>
        /// <param name="path">The path to the new folder to create, relative to root.</param>
        /// <param name="root">Specify the root relative to which path is specified. Valid values are dropbox and sandbox.</param>
        public void fileops_create_folder(string path, string root = "dropbox")
        {
            List<string> parms = new List<string>();
            parms.Add("path=" + path);
            parms.Add("root=" + root);
            string requesturl = helper.OAuthRequestUrl(API_ROOT + API_FILEOPS_CREATE_FOLDER, parms);


            Helper.HttpWebRequestGet((target) =>
            {
                HttpWebRequest wr = (HttpWebRequest)target.AsyncState;
                HttpWebResponse response = (HttpWebResponse)wr.EndGetResponse(target);
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    string sr = new StreamReader(response.GetResponseStream()).ReadToEnd();

                    FolderMetadata metadata = Helper.Deserialize<FolderMetadata>(sr);
                    try
                    {
                        FileopsCreateFolderComplete.Invoke(metadata);
                    }
                    catch
                    {
                    }
                }
                else
                {
                    throw new DropboxError((DropboxStatusCode)(int)response.StatusCode);
                }
            }, requesturl);
        }

        /// <summary>
        /// Deletes a file or folder
        /// </summary>
        /// <param name="path">path specifies either a file or folder to be deleted. This path is interpreted relative to the location specified by root.</param>
        /// <param name="root">Specify the root relative to which path is specified. Valid values are dropbox and sandbox.</param>
        public void fileops_delete(string path, string root = "dropbox")
        {
            List<string> parms = new List<string>();
            parms.Add("path=" + path);
            parms.Add("root=" + root);
            string requesturl = helper.OAuthRequestUrl(API_ROOT + API_FILEOPS_DELETE, parms);
            Helper.HttpWebRequestGet((target) =>
            {
                HttpWebRequest wr = (HttpWebRequest)target.AsyncState;
                HttpWebResponse response = (HttpWebResponse)wr.EndGetResponse(target);
                if (response.StatusCode == HttpStatusCode.OK)
                {               
                    try
                    {
                        FileopsDeleteComplete.Invoke();
                    }
                    catch
                    {
                    }
                }
                else
                {
                    throw new DropboxError((DropboxStatusCode)(int)response.StatusCode);
                }
            }, requesturl);
        }

        /// <summary>
        /// Move a file or folder to a new location.
        /// </summary>
        /// <param name="from_path">specifies either a file or folder to be copied to the location specified by to_path. This path is interpreted relative to the location specified by root.</param>
        /// <param name="to_path">specifies the destination path including the new name for file or folder. This path is interpreted relative to the location specified by root.</param>
        /// <param name="root">Specify the root relative to which from_path and to_path are specified. Valid values are dropbox and sandbox.</param>
        public void fileops_move(string from_path, string to_path, string root = "dropbox")
        {
            List<string> parms = new List<string>();
            parms.Add("from_path=" + from_path);
            parms.Add("to_path=" + to_path);
            parms.Add("root=" + root);
            string requesturl = helper.OAuthRequestUrl(API_ROOT + API_FILEOPS_MOVE, parms);


            Helper.HttpWebRequestGet((target) =>
            {
                HttpWebRequest wr = (HttpWebRequest)target.AsyncState;
                HttpWebResponse response = (HttpWebResponse)wr.EndGetResponse(target);
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    string sr = new StreamReader(response.GetResponseStream()).ReadToEnd();

                    FolderMetadata metadata = Helper.Deserialize<FolderMetadata>(sr);
                    try
                    {
                        FileopsMoveComplete.Invoke(metadata);
                    }
                    catch
                    {
                    }
                }
                else if ((int)response.StatusCode == (int)DropboxStatusCode.PathNotFound)
                {
                    throw new DropboxError((DropboxStatusCode)(int)response.StatusCode, "Failed to find path (" + from_path + ")");
                }
                else
                {
                    throw new DropboxError((DropboxStatusCode)(int)response.StatusCode);
                }
            }, requesturl);
        }
    }
}